import  { AxiosInstance } from 'axios';
import { HttpCacheUtil } from './cache/http_cache_util';
import { bdjfLog } from './log_util';
import { copyRes, createLoadingResWithCode, createRes, ResponseConstructor, ResponseImpl } from './response';
import { RESPONSE_CACHE_ERROR, RESPONSE_CATCH_ERROR, RESPONSE_CODE_NO_DATA, RESPONSE_CODE_NO_RESPONSE } from './system_constant';
import { BdjfRequest, BdjfResponse, HttpConfig } from './type';
import { getCacheKey } from './utils';


export class BdjfHttp {


    public axiosInstance: AxiosInstance;

    public config: HttpConfig;

    public cacheUtil: HttpCacheUtil|undefined;

    constructor(axiosInstance: AxiosInstance, config: HttpConfig) {
        this.axiosInstance = axiosInstance;
        this.config = config;
    }

    public createLoadingRes = <T= any>(data?: any) => {
        const successCode = this.config.successCode;
        return createLoadingResWithCode<T>(successCode, data);
    }

    public sendRequest = <T= any>(requestData: BdjfRequest): Promise<BdjfResponse<T>> => {
        if (requestData.requestOption.needCache) {
            return this.cacheRequest<T>(requestData);
        } else {
            return this.networkRequest<T>(requestData);
        }
    }

    private createErrorRes = <T= any>(resConfig: ResponseConstructor<T>) => {
        const successCode = this.config.successCode;
        return new ResponseImpl<T>({
            ...resConfig,
            successCode,
        });
    }

    private log = (...data: any[]) => {
        bdjfLog(this.config.openLog, ...data);
    }

    private cacheRequest = <T>(requestData: BdjfRequest): Promise<BdjfResponse<T>> => {


        const url = requestData.requestOption.url;

        const log = this.log;

        if (!this.cacheUtil) {
            this.cacheUtil = new HttpCacheUtil(this.config);
        }

        const cacheUtil = this.cacheUtil;

        const key: string | Record<string, any> = getCacheKey(requestData);

        try {
            const cacheData = cacheUtil.getCacheDataByKey<T>(url, key);
            if (cacheData && cacheData.success) {
                log('---从存储中获取数据---', cacheData);
                return Promise.resolve(cacheData);
            }
        } catch (error: any) {
            const errorRes = this.createErrorRes({
                code: RESPONSE_CACHE_ERROR,
                msg: error.message,
                data: error,
            });
            return Promise.resolve(errorRes);
        }


        return new Promise( (resolve, reject) => {
            this.networkRequest<T>(requestData)
                .then((res: BdjfResponse<T>) => {
                    log('---从网络获得数据---', res);
                    if (res.success) {
                        if (res.data instanceof Array) {
                            if (res.data.length > 0) {
                                cacheUtil.setCacheData(url, key, copyRes<T>(res));
                            }
                        } else {
                            if (res.data) {
                                cacheUtil.setCacheData(url, key, copyRes<T>(res));
                            }
                        }
                    }
                    resolve(res);
                }).catch((e) => {
                    reject(e);
                });
        });
    }

    private networkRequest = <T>(requestData: BdjfRequest): Promise<BdjfResponse<T>> => {
        return new Promise((resolve, reject) => {

            const config = this.config;

            const createErrorRes = this.createErrorRes;

            if (!requestData.requestOption.hideLoading) {
                config.showLoading(requestData);
            }

            this.axiosInstance(requestData.requestOption).then((res) => {
                let responseData: BdjfResponse<T>;

                if (!requestData.requestOption.hideLoading) {
                    this.config.hideLoading(requestData);
                }

                if (res === undefined) {
                    responseData = createErrorRes<T>({
                        code: RESPONSE_CODE_NO_RESPONSE,
                        msg: 'no response',
                    });
                    config.showError(responseData, requestData);
                } else {
                    if (res.status === 200) {
                        const data: any = res.data;
                        if (data) {
                            //  判断数据是否可解析，当有文件类型或arraybuffer类型时，原样返回
                            if (!res.config.responseType || res.config.responseType === 'json') {
                                responseData = createRes<T>(config.successCode, data, requestData.requestOption.attributeMapping || config.attributeMapping);
                                if (responseData.success) {
                                    if (this.cacheUtil && requestData.cacheOption?.conflictCacheUrl) {
                                        this.cacheUtil.clearObsoleteCacheData(requestData.conflictCacheUrl());
                                    }
                                } else {
                                     if (!requestData.requestOption.hideWarn) {
                                        config.showWarn(responseData, requestData);
                                     }
                                }
                            } else {
                                responseData = new ResponseImpl<T>({
                                    code: config.successCode,
                                    data: res.data,
                                });
                            }
                        } else {
                            responseData = createErrorRes<T>({
                                code: RESPONSE_CODE_NO_DATA,
                                msg: 'no data',
                            });
                            config.showError(responseData, requestData);
                        }
                    } else {
                        responseData = createErrorRes<T>({
                            code: res.status,
                            msg: 'no data',
                        });
                        config.showError(responseData, requestData);
                    }
                }

                responseData._originalData_ = res;
                resolve(responseData);
            }).catch((e) => {
                const errorResponse = createErrorRes<T>({
                    code: RESPONSE_CATCH_ERROR,
                    msg: e.message,
                    data: e,
                });
                if (!requestData.requestOption.hideLoading) {
                    config.hideLoading(requestData);
                }
                if (!requestData.requestOption.hideError) {
                    config.showError(errorResponse, requestData);
                }

                errorResponse._originalData_ = e;

                if (requestData.requestOption.rejectError) {
                    reject(errorResponse);
                } else {
                    resolve(errorResponse);
                }
            });
        });
    }

}
